package cn.ulyer.core.secure.authority;

import cn.hutool.core.lang.UUID;
import cn.ulyer.common.constants.AuthStrategyEnum;
import cn.ulyer.common.jpa.entity.Permission;
import cn.ulyer.common.security.authority.AuthorityProvider;
import cn.ulyer.common.security.authority.AuthorityService;
import cn.ulyer.common.security.authority.AuthorityHandlerStrategy;
import cn.ulyer.core.secure.authority.strategy.AuthenticatedHandlerStrategy;
import cn.ulyer.core.secure.authority.strategy.DefaultHandlerStrategy;
import cn.ulyer.core.secure.authority.strategy.IpHandlerStrategy;
import cn.ulyer.core.secure.authority.strategy.NoAuthHandlerStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Slf4j
public class UrlAuthorityService implements AuthorityService {


    final AntPathMatcher antPathMatcher = new AntPathMatcher();

    Map<String, AuthorityHandlerStrategy> handlerStrategies = new HashMap<>();

    private DefaultHandlerStrategy defaultHandlerStrategy = new DefaultHandlerStrategy();

    private AuthorityProvider authorityProvider;

    public UrlAuthorityService(AuthorityProvider authorityProvider, Collection<AuthorityHandlerStrategy> customStrategies) {
        this.authorityProvider = authorityProvider;
        this.handlerStrategies.put(AuthStrategyEnum.NO_AUTH.name(), new NoAuthHandlerStrategy());
        this.handlerStrategies.put(AuthStrategyEnum.AUTHENTICATED.name(), new AuthenticatedHandlerStrategy());
        this.handlerStrategies.put(AuthStrategyEnum.ANY_IP.name(), new IpHandlerStrategy());
        this.handlerStrategies.put(AuthStrategyEnum.ANY_ROLE.name(), defaultHandlerStrategy);
        if (customStrategies != null) {
            for (AuthorityHandlerStrategy customStrategy : customStrategies) {
                if (!handlerStrategies.containsKey(customStrategy.strategy())) {
                    handlerStrategies.put(customStrategy.strategy(), customStrategy);
                }
            }
        }
        log.info("register {} authorityHandler", handlerStrategies.size());
    }


    @Override
    public boolean hasAuthority(HttpServletRequest request, Authentication authentication) throws Exception {
        String requestUrl = request.getRequestURI().replace(request.getContextPath(), "");
        Collection<Permission> allPermissions = authorityProvider.providerPermissions();
        int handlerResult = 0;
        boolean matched = false;
        for (Permission permission : allPermissions) {
            String matchUrl = permission.getUrl();
            if (antPathMatcher.match(matchUrl, requestUrl)) {
                String authStrategy = permission.getAuthStrategy();
                AuthorityHandlerStrategy authorityHandlerStrategy = handlerStrategies.get(authStrategy);
                if (authorityHandlerStrategy == null) {
                    authorityHandlerStrategy = defaultHandlerStrategy;
                }
                boolean authSuccess = false;
                try {
                    authSuccess = authorityHandlerStrategy.handler(request, authentication, permission);
                } catch (AccessDeniedException | AuthenticationException e) {
                    log.warn("strategy class throw exception:{}", e);
                } catch (Exception e) {
                    throw e;
                }
                handlerResult += authSuccess ? 1 : -1;
                matched = true;
            }
        }
        //不在管控列表则通过
        if (matched && handlerResult <= 0) {
            throw new AccessDeniedException("no role access resource with path :" + requestUrl);
        }
        if (authentication == null) {
            String idKey = UUID.fastUUID().toString();
            AnonymousAuthenticationToken anonymous = new AnonymousAuthenticationToken(idKey, idKey, Collections.EMPTY_SET);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        return true;
    }


}
